/*******************************************************************************
* This file provides the API for BMP085 pressure sensor.
*
* Author: Kong Wai Weng
* Email:  waiweng83@gmail.com
*******************************************************************************/



#include "hal_bmp085.h"



/*******************************************************************************
* PRIVATE CONSTANT DEFINITION                                                  *
*******************************************************************************/

// I2C slave address.
#define I2C_ADD_PRES	0x77

// Register address.
#define PRES_EEPROM		0xaa
#define PRES_CONTROL	0xf4
#define PRES_DATA		0xf6

// Control register value.
#define PRES_TEMP		0x2e
#define PRES_LOW		0x34
#define PRES_STANDARD	0x74
#define PRES_HIGH		0xb4
#define PRES_ULTRA_HIGH	0xf4

// The constant to compute the altitude.
// 288.15 / 0.00198122 * 0.3048
#define ALTITUDE_CONSTANT	44330.32172f



/*******************************************************************************
* PRIVATE FUNCTION PROTOTYPES                                                  *
*******************************************************************************/

static void prv_vStartMeasureTemp(void);
static void prv_vStartMeasurePress(void);
static void prv_vReadTempRaw(void);
static void prv_vReadPressRaw(void);
static void prv_vCalculate(void);



/*******************************************************************************
* PRIVATE GLOBAL VARIABLES                                                     *
*******************************************************************************/

// Calibration Coefficients.
static signed int siAc1 = 0;
static signed int siAc2 = 0;
static signed int siAc3 = 0;
static unsigned int uiAc4 = 0;
static unsigned int uiAc5 = 0;
static unsigned int uiAc6 = 0;
static signed int siB1 = 0;
static signed int siB2 = 0;
static signed int siMb = 0;
static signed int siMc = 0;
static signed int siMd = 0;

// Oversampling setting.
static unsigned char ucOss = 0;

// Raw data.
static volatile signed long slTemperatureRaw = 0;
static volatile signed long slPressureRaw = 0;

// Calculated data.
static volatile float fTemperature = 0;
static volatile float fPressure = 0;
static volatile float fAltitude = 0;



/*******************************************************************************
* PUBLIC FUNCTION: vInitBmp085
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Initialize the BMP085 pressure sensor.
* This function must be called after the I2C 1 has been initialized.
*
*******************************************************************************/
void vInitBmp085(void)
{	
	// Oversampling setting = 3 (Ultra high resolution).
	ucOss = 3;
	
	// Read the calibration coefficients.
	unsigned char ucBuffer[22];
	if (uiReadI2c1(I2C_ADD_PRES, PRES_EEPROM, 22, ucBuffer) != 0) {
		xSystemError.bPressureError = 1;
	}
	
	siAc1 = ((signed int)ucBuffer[0] << 8) | (signed int)ucBuffer[1];
	siAc2 = ((signed int)ucBuffer[2] << 8) | (signed int)ucBuffer[3];
	siAc3 = ((signed int)ucBuffer[4] << 8) | (signed int)ucBuffer[5];
	uiAc4 = ((unsigned int)ucBuffer[6] << 8) | (unsigned int)ucBuffer[7];
	uiAc5 = ((unsigned int)ucBuffer[8] << 8) | (unsigned int)ucBuffer[9];
	uiAc6 = ((unsigned int)ucBuffer[10] << 8) | (unsigned int)ucBuffer[11];
	siB1 = ((signed int)ucBuffer[12] << 8) | (signed int)ucBuffer[13];
	siB2 = ((signed int)ucBuffer[14] << 8) | (signed int)ucBuffer[15];
	siMb = ((signed int)ucBuffer[16] << 8) | (signed int)ucBuffer[17];
	siMc = ((signed int)ucBuffer[18] << 8) | (signed int)ucBuffer[19];
	siMd = ((signed int)ucBuffer[20] << 8) | (signed int)ucBuffer[21];
	
	// Start measuring the temperature.
	prv_vStartMeasureTemp();
}	



/*******************************************************************************
* PUBLIC FUNCTION: fGetTemperature
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ Temperature (Unit = degree celcius).
*
* DESCRIPTIONS:
* Read the calculated temperature.
*
*******************************************************************************/
float fGetTemperature(void)
{
	float fBuffer;
	
	INTERRUPT_PROTECT(fBuffer = fTemperature);
	return fBuffer;
}	



/*******************************************************************************
* PUBLIC FUNCTION: fGetAbsolutePressure
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ Absolute pressure (Unit = Pa).
*
* DESCRIPTIONS:
* Read the calculated absolute pressure.
*
*******************************************************************************/
float fGetAbsolutePressure(void)
{
	float fBuffer;
	
	INTERRUPT_PROTECT(fBuffer = fPressure);
	return fBuffer;
}	



/*******************************************************************************
* PUBLIC FUNCTION: fGetPressureAltitude
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ Altitude in m.
*
* DESCRIPTIONS:
* Read the calculated pressure altitude.
*
*******************************************************************************/
float fGetPressureAltitude(void)
{
	float fBuffer;
	
	INTERRUPT_PROTECT(fBuffer = fAltitude);
	return fBuffer;
}



/*******************************************************************************
* TASK: taskBmp085Loop
*
* DESCRIPTIONS:
* This task is the main loop to read the data from the BMP085 pressure sensor.
* The temperature will be read once every 4 pressure data being read.
*
*******************************************************************************/
portTASK_FUNCTION(taskBmp085Loop, pvParameters)
{
	static unsigned int uiLoopNumber = 0;
	static unsigned int uiLoopCount = 0;
	static float fRefPressure = 0.0f;
	
	// We need to initialize xLastFlashTime prior to the first call to vTaskDelayUntil().
	portTickType xLastFlashTime = xTaskGetTickCount();
	
	while (1) {
		// Make sure the EOC is set.
		if (BMP085_EOC == 1) {
			if (uiLoopNumber == 0) {
				// First reading is temperature.
				prv_vReadTempRaw();
				
				// Next reading is pressure.
				prv_vStartMeasurePress();
				
				uiLoopNumber++;
			}	
			
			else if (uiLoopNumber == 4) {
				// Last reading is pressure.
				prv_vReadPressRaw();
				
				// Next reading is temperature.
				prv_vStartMeasureTemp();
				
				// Calculate the true temperature and pressure.
				prv_vCalculate();
				
				// Reset the loop counter.
				uiLoopNumber = 0;
			}	
			
			else {
				// Reading 1 - 3 is pressure.
				prv_vReadPressRaw();
				
				// Next reading is pressure.
				prv_vStartMeasurePress();
				
				// Calculate the true temperature and pressure.
				prv_vCalculate();
				
				uiLoopNumber++;
			}	
		}	
		
		
		
		// Don't do anything for the first 2 seconds.
		if (uiLoopCount < (2 * 50)) {
			uiLoopCount++;
		}
		// Calculate the average for pressure reference for another 3 seconds.
		else if (uiLoopCount < (5 * 50)) {
			uiLoopCount++;
			
			fRefPressure += fGetAbsolutePressure();
		}
		// After 5 seconds...
		else {
			// Calculate the average pressure reference for the first time.
			if (xSystemState.bAltitudeReady == 0) {
				fRefPressure /= (5.0f - 2.0f) * 50.0f;
			}	
			
			// Calculate the altitude in meter.
			float fAltitudeTemp = (1.0f - powf(fGetAbsolutePressure() / fRefPressure, 0.19026f)) * ALTITUDE_CONSTANT;
			INTERRUPT_PROTECT(fAltitude = fAltitudeTemp);
			
			// Indicate the altitude sensor is ready.
			xSystemState.bAltitudeReady = 1;
		}	
		
		
		
		// Record the stack high water mark.
		xStackWaterMark.uiBmp085Loop = uxTaskGetStackHighWaterMark(NULL);
		
		// Set the error flag if the water mark is too low.
		if (xStackWaterMark.uiBmp085Loop < RTOS_STACK_THRESHOLD) {
			xSystemError.bRtosStackError = 1;
		}
		
		// Loop every 20ms (50Hz).
		vTaskDelayUntil(&xLastFlashTime, configTICK_RATE_HZ / 50);
	}	
}	



/*******************************************************************************
* PRIVATE FUNCTION: prv_vStartMeasureTemp
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Send command to start measuring the temperature.
*
*******************************************************************************/
static void prv_vStartMeasureTemp(void)
{
	unsigned char ucData = PRES_TEMP;
	if (uiWriteI2c1(I2C_ADD_PRES, PRES_CONTROL, 1, &ucData) != 0) {
		xSystemError.bPressureError = 1;
	}	
}	



/*******************************************************************************
* PRIVATE FUNCTION: prv_vStartMeasurePress
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Send command to start measuring the pressure.
*
*******************************************************************************/
static void prv_vStartMeasurePress(void)
{
	unsigned char ucData = PRES_LOW + (ucOss << 6);
	if (uiWriteI2c1(I2C_ADD_PRES, PRES_CONTROL, 1, &ucData) != 0) {
		xSystemError.bPressureError = 1;
	}	
}	



/*******************************************************************************
* PRIVATE FUNCTION: prv_vReadTempRaw
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Read the raw temperature data from BMP085.
*
*******************************************************************************/
static void prv_vReadTempRaw(void)
{
	unsigned char aucBuffer[2];
	
	// Read the data.
	if (uiReadI2c1(I2C_ADD_PRES, PRES_DATA, 2, aucBuffer) != 0) {
		xSystemError.bPressureError = 1;
		return;
	}	
	
	// Convert the data into unsigned int.
	slTemperatureRaw = ((signed long)aucBuffer[0] << 8) | (signed long)aucBuffer[1];
}	



/*******************************************************************************
* PRIVATE FUNCTION: prv_vReadPressRaw
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Read the raw pressure data from BMP085.
*
*******************************************************************************/
static void prv_vReadPressRaw(void)
{
	unsigned char aucBuffer[3];
	
	// Read the data.
	if (uiReadI2c1(I2C_ADD_PRES, PRES_DATA, 3, aucBuffer) != 0) {
		xSystemError.bPressureError = 1;
		return;
	}	
	
	// Convert the data into unsigned long.
	slPressureRaw = (((signed long)aucBuffer[0] << 16) |
					 ((signed long)aucBuffer[1] << 8) |
					 ((signed long)aucBuffer[2])) >> (8 - ucOss);
}	



/*******************************************************************************
* PRIVATE FUNCTION: prv_vCalculate
*
* PARAMETERS:
* ~ void
*
* RETURN:
* ~ void
*
* DESCRIPTIONS:
* Calculate the true temperature and pressure data.
* Based on formulas in page 13 of the BMP085 datasheet.
*
*******************************************************************************/
static void prv_vCalculate(void)
{
	signed long slX1;
	signed long slX2;
	signed long slX3;
	signed long slB3;
	signed long slB5;
	signed long slB6;
	signed long slP;
	
	unsigned long ulB4;
	unsigned long ulB7;
	
	
	
	// Calculate the true temperature.
	slX1 = (slTemperatureRaw - (signed long)uiAc6) * (signed long)uiAc5 >> 15;
	slX2 = ((signed long)siMc << 11) / (slX1 + (signed long)siMd);
	slB5 = slX1 + slX2;
	float fTemperatureTemp = (float)((slB5 + 8L) >> 4) * 0.1f;
	INTERRUPT_PROTECT(fTemperature = fTemperatureTemp);
	
	// Calculate the true pressure.
	slB6 = slB5 - 4000L;
	slX1 = ((signed long)siB2 * (slB6 * slB6 >> 12)) >> 11;
	slX2 = (signed long)siAc2 * slB6 >> 11;
	slX3 = slX1 + slX2;
	slB3 = ((((signed long)siAc1 * 4L + slX3) << ucOss) + 2) >> 2;
	slX1 = (signed long)siAc3 * slB6 >> 13;
	slX2 = ((signed long)siB1 * (slB6 * slB6 >> 12)) >> 16;
	slX3 = ((slX1 + slX2) + 2) >> 2;
	ulB4 = ((unsigned long)uiAc4 * (unsigned long)(slX3 + 32768L)) >> 15;
	ulB7 = ((unsigned long)slPressureRaw - (unsigned long)slB3) * (50000L >> ucOss);
	slP = (signed long)((ulB7 < 0x80000000) ? (ulB7 * 2L) / ulB4 : (ulB7 / ulB4) * 2L);
	slX1 = (slP >> 8) * (slP >> 8);
	slX1 = (slX1 * 3038L) >> 16;
	slX2 = (-7357 * slP) >> 16;
	float fPressureTemp = ((float)(slP + ((slX1 + slX2 + 3791L) >> 4)) * 0.3) + (fGetAbsolutePressure() * 0.7);
	INTERRUPT_PROTECT(fPressure = fPressureTemp);
}	
